package xyz.xpman.prisonerfitness.entity;

import android.annotation.SuppressLint;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Build;

import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;

import org.jetbrains.annotations.NotNull;

import java.io.Serializable;
import java.util.Set;
import java.util.function.Function;

import xyz.xpman.prisonerfitness.sqlite.DbHelper;
import xyz.xpman.prisonerfitness.util.EncryptUtil;
import xyz.xpman.prisonerfitness.util.StrKit;


@RequiresApi(api = Build.VERSION_CODES.O)
@SuppressWarnings("unused")
public class User implements Serializable {
    public static final String TABLE_NAME = "user";
    public static final String ID_CLAUSE = "id = ?";

    public static final String ID = "id";
    public static final String USERNAME = "username";
    public static final String PASSWORD = "password";
//    public static final String ROLE_NAME = "role";
    public static final String SEX_NAME = "sex";
    public static final String SEX_MAN = "男";
    public static final String SEX_WOMAN = "女";
    public static final String SEX_SECRECY = "保密";

    public static final String ROLE_STUDENT = "学生";
    public static final String ROLE_TEACHER = "教职工";
    public static final String ROLE_ROOT = "root";

    public static final Set<String> SEX_LIMIT = Set.of(
            SEX_MAN, SEX_WOMAN, SEX_SECRECY);

//    public static final Set<String> ROLE_LIMIT = Set.of(
//          ROLE_ROOT, ROLE_STUDENT, ROLE_TEACHER);

    public static final String[] COLS = {ID, USERNAME, PASSWORD, SEX_NAME};
    public static final String COLS_STR = String.join(", ", COLS);

    public static final String[] PUBLIC_COLS = {ID, USERNAME, SEX_NAME};
    public static final String PUBLIC_COLS_STR = String.join(", ", PUBLIC_COLS);

    private static boolean init = false;
    private static SQLiteDatabase db = null;

    private Integer id;
    private String username;
    private String password;
    private String newPassword;
    private String sex;


    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -



    public User() {
    }

    public static void init(@Nullable Context context) {
        if (!init) {
            db = new DbHelper(context).getReadableDatabase();
            init = true;
        }
    }


    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -


    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    public String checkForAdd() {
        String message = checkUsernameAndPassword();

        if (StrKit.notBlank(message)) {
            return message;
        }

        if (!SEX_LIMIT.contains(sex)) {
            return "非法角色!";
        }

        return message;
    }

    public String checkUsernameAndPassword() {
        String message = "";

        if (StrKit.isBlank(username)) {
            return "用户名不能为空!";
        }

        if (username.length() < 3 || username.length() > 16) {
            return "用户名应为2-16个字符!";
        }

        if (StrKit.isBlank(password)) {
            return "密码不能为空!";
        }

        if (password.length() < 8 || password.length() > 32) {
            return "密码应为8-32个字符!";
        }

        return message;
    }



    /**
     * 修改密码
     * @return success or failure
     */
    @RequiresApi(api = Build.VERSION_CODES.O)
    public boolean loginByUsernameAndPassword() {
        final String needEncryptPasswd = EncryptUtil.encryptSha(password);
        final String sqlFmt = "select %s from %s where username = ? and password = ?";
        final String sql = String.format(sqlFmt, PUBLIC_COLS_STR, TABLE_NAME);

        @SuppressLint("Recycle")
        Cursor cursor = db.rawQuery(sql, new String[]{username, needEncryptPasswd});

        boolean temp = (null != processCursor(cursor, this::updateSelfByCursor));
        password = null;
        return temp;
    }


    public boolean register() {
        return add();
    }

    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -


    public Integer getId() {
        return id;
    }

    public String getIdStr() {
        return String.valueOf(id);
    }

    public void setId(int id) {
        this.id = id;
    }



    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getNewPassword() {
        return newPassword;
    }

    public void setNewPassword(String newPassword) {
        this.newPassword = newPassword;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }


    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -


    public boolean isRoot() {
        return ROLE_ROOT.equals(getUsername());
    }


    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -


    public User byCursor(@NotNull Cursor cursor) {
        User user = new User();
        cursor.moveToFirst();
        int index = 0;

        for (String colName : cursor.getColumnNames()) {

            if (ID.equals(colName)) {
                user.setId(cursor.getInt(index));
            } else if (USERNAME.equals(colName)) {
                user.setUsername(cursor.getString(index));
            } else if (SEX_NAME.equals(colName)) {
                user.setSex(cursor.getString(index));
            }

            index++;
        }

        return user;
    }

    public User updateSelfByCursor(@NotNull Cursor cursor) {
        return updateSelfByCursor(this, cursor);
    }

    public User updateSelfByCursor(@NotNull User user, @NotNull Cursor cursor) {
        cursor.moveToFirst();
        int index = 0;

        for (String colName : cursor.getColumnNames()) {

            if (ID.equals(colName)) {
                user.setId(cursor.getInt(index));
            } else if (USERNAME.equals(colName)) {
                user.setUsername(cursor.getString(index));
            } else if (SEX_NAME.equals(colName)) {
                user.setSex(cursor.getString(index));
            }

            index++;
        }

        return this;


/*
        int i = cursor.getColumnIndex(ID);

        if (i != -1) {
            String id = cursor.getString(i);

            if (StrKit.notBlank(id)) {
                user.setId(Integer.parseInt(id));
            }
        }



        i = cursor.getColumnIndex(USERNAME);

        if (i != -1) {
            String username = cursor.getString(i);
            user.setUsername(cursor.getString(i));
        }



        i = cursor.getColumnIndex(ROLE_NAME);

        if (i != -1) {
            String role = cursor.getString(i);
            user.setRoleName(cursor.getString(i));
        }

        return this;*/
    }




    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -


    @RequiresApi(api = Build.VERSION_CODES.O)
    public User findById(String id) {
        final String sqlFmt = "select %s from %s where %s";

        final String sql = String.format(sqlFmt,
                PUBLIC_COLS_STR, TABLE_NAME, ID_CLAUSE);

        @SuppressLint("Recycle")
        Cursor cursor = db.rawQuery(sql, new String[]{ id });

        return processCursor(cursor, this::byCursor);
    }

    @RequiresApi(api = Build.VERSION_CODES.O)
    public User findByUsername(String username) {
        final String sqlFmt = "select %s from %s where username = ?";

        final String sql = String.format(sqlFmt, PUBLIC_COLS_STR, TABLE_NAME);

        @SuppressLint("Recycle")
        Cursor cursor = db.rawQuery(sql, new String[]{ username });

        return processCursor(cursor, this::byCursor);
    }

    /**
     * please checkForAdd first
     * @return add success or failure
     */
    public boolean add() {
        // 注: ContentValue = HashMap<String, BaseType or String>
        ContentValues values = new ContentValues();

        values.put(USERNAME, username);
        values.put(PASSWORD, EncryptUtil.encryptSha(password));
        values.put(SEX_NAME, sex);

        return -1 != db.insert(TABLE_NAME, null, values);
    }

    /**
     * 更新用户信息，普通不能更新其他账户及自己的id、用户名，管理员可以修改用户名称、重置用户密码等
     * todo 待完善
     * @return success or failure
     */
    public boolean update() {
        ContentValues values = new ContentValues();

        values.put(SEX_NAME, sex);

        return 1 == db.update(TABLE_NAME, values, ID_CLAUSE,
                new String[]{ String.valueOf(id) });
    }

    /**
     * 重置密码, 需提前校验授权
     * @return success or failure
     */
    @RequiresApi(api = Build.VERSION_CODES.O)
    public boolean resetPassword() {
        ContentValues values = new ContentValues();

        values.put(PASSWORD, EncryptUtil.encryptSha(newPassword));

        return 1 == db.update(TABLE_NAME, values, ID_CLAUSE,
                new String[]{ String.valueOf(id) });
    }

    /**
     * 修改密码
     * @return success or failure
     */
    @RequiresApi(api = Build.VERSION_CODES.O)
    public boolean modifyPassword() {
        final String whereClause = "id = ? and password = ?";

        ContentValues values = new ContentValues();

        values.put(ID, id);
        values.put(PASSWORD, EncryptUtil.encryptSha(newPassword));

        String needEncryptPasswd = EncryptUtil.encryptSha(password);

        return 1 == db.update(TABLE_NAME, values, whereClause,
                new String[]{ String.valueOf(id), needEncryptPasswd});
    }


    public boolean deleteById(String id) {
        return 1 == db.delete(TABLE_NAME, ID_CLAUSE, new String[]{ id });
    }




    public boolean deleteNotRoot() {
        final String whereNotRoot = "username != ?";
        return 0 < db.delete(TABLE_NAME, whereNotRoot, new String[]{ "root" });
    }


    private <T> T processCursor(Cursor cursor, Function<Cursor, T> func) {
        T obj = null;

        if (null != cursor && 1 == cursor.getCount()) {
            try {
                obj = func.apply(cursor);
            } finally {
                cursor.close();
            }
        }
        return obj;
    }

}
